#include "libCache.h"

static memCache_t* ultimaPosicion = NULL;

memCache_t *inicializarCache(uint32_t size){
	memCache_t *cache = malloc(sizeof(memCache_t));
	cache->estado = 'L';
	cache->key = NULL;
	cache->posInicio = 0;
	cache->tamanio = size;
	cache->sig = NULL;
	return cache;
}

memCache_t *worstFit(memCache_t *memoria, uint32_t requiredSize){
	memCache_t *retorno = memoria;
	while(memoria!= NULL){
		if(memoria->estado == 'L' && memoria->tamanio >= requiredSize && memoria->tamanio > retorno->tamanio){
			retorno = memoria;
		}
		memoria = memoria->sig;
	}
	if(retorno->estado == 'O' || retorno->tamanio < requiredSize){
		retorno = NULL;
	}
	return retorno;

}

void ordenarLista(memCache_t** memoria){
	memCache_t *auxMemoria = (*memoria)->sig;
	memCache_t *antMemoria = *memoria;
	memCache_t *nodoAEliminar = NULL;
	uint32_t chunksLibres = 0;
	if(antMemoria->estado == 'L'){
		nodoAEliminar = antMemoria;
		antMemoria = antMemoria->sig;
		auxMemoria = auxMemoria->sig;
		*memoria = antMemoria;
		chunksLibres += nodoAEliminar->tamanio;
		free(nodoAEliminar->key);
		nodoAEliminar->key = NULL;
		free(nodoAEliminar);
	}
	while(auxMemoria != NULL){
		if(auxMemoria->estado == 'L' ){
			antMemoria->sig = auxMemoria->sig;
			nodoAEliminar = auxMemoria;;
			chunksLibres += nodoAEliminar->tamanio;
			free(nodoAEliminar->key);
			nodoAEliminar->key = NULL;
			free(nodoAEliminar);
		}
		auxMemoria = auxMemoria->sig;
		antMemoria = antMemoria->sig;
	}
	if(chunksLibres > 0){
		memCache_t *nodoFinal = malloc(sizeof(memCache_t));
		(*nodoFinal).estado = 'L';
		(*nodoFinal).tamanio = chunksLibres;
		(*nodoFinal).sig = NULL;
		antMemoria->sig = nodoFinal;
		(*nodoFinal).key = NULL;
	}
	return;
}

char* leerCache(char* cache, uint32_t inicio, uint32_t tamanio){
	/*estar atentos a memcached y los centinelas*/
	char* infoCache = malloc(tamanio);
	memcpy(infoCache, cache+inicio, tamanio);
	return infoCache;
}

void escribirCache(char* cache, uint32_t inicio, uint32_t tamanio, const char* info){
	memcpy(cache+inicio, info, tamanio);
	return;
}


void sincronizarCache(memCache_t *lstMemoria, char* cache){
	uint32_t i = 0;
	while(lstMemoria->estado == 'O'){
		char* infoCache = leerCache(cache, (lstMemoria->posInicio * CHUNKSIZE), lstMemoria->tamanio * CHUNKSIZE);
		escribirCache(cache, i, (lstMemoria->tamanio * CHUNKSIZE), infoCache);
		lstMemoria->posInicio = i/CHUNKSIZE;
		i += lstMemoria->tamanio * CHUNKSIZE;
		lstMemoria = lstMemoria->sig;
	}
	lstMemoria->posInicio = i;
	return;
}

void compactarCache(char* cache,memCache_t ** lstMemoria){
	ordenarLista(lstMemoria);
	sincronizarCache(*lstMemoria, cache);
}

void unirNodosLibres(memCache_t *nodo1, memCache_t **nodo2){
	nodo1->tamanio += (*nodo2)->tamanio;
	nodo1-> sig = (*nodo2)->sig;
	if((*nodo2)->key != NULL){
		free((*nodo2)->key);
		(*nodo2)->key = NULL;
	}
	free(*nodo2);
	return;
}


void separarNodoLibre(memCache_t *nodoASeparar, uint32_t size){
	memCache_t *nuevoNodo  = malloc(sizeof(memCache_t));
	nodoASeparar->estado = 'O';
	nuevoNodo->estado = 'L';
	nuevoNodo->key = NULL;
	nuevoNodo->posInicio = nodoASeparar->posInicio + size;
	nuevoNodo->sig = nodoASeparar->sig;
	nuevoNodo->tamanio = nodoASeparar->tamanio - size;
	nodoASeparar->tamanio = size;
	nodoASeparar->sig = nuevoNodo;
	return;
}


memCache_t *NextFit(memCache_t *memoria, uint32_t requiredSize){
	if(ultimaPosicion == NULL) ultimaPosicion = memoria;
	while(ultimaPosicion != NULL){
		if(ultimaPosicion->tamanio >= requiredSize){
			return ultimaPosicion;
		} else {
			ultimaPosicion = ultimaPosicion->sig;
		}
	}
	return NULL;
}


void cargarCache(memCache_t* espacioASetear, uint32_t cantChunks,
		const char* key, diccionario_t diccionario, const char* value) {
	if (espacioASetear->tamanio != cantChunks) {
		separarNodoLibre(espacioASetear, cantChunks);
	}
	espacioASetear->estado = 'O';
	espacioASetear->tiempo = time(NULL);
	espacioASetear->key = malloc(strlen(key) + 1);
	memset(espacioASetear->key, '\0', strlen(key) + 1);
	memcpy(espacioASetear->key, key, strlen(key));
	escribirCache(diccionario.cache, espacioASetear->posInicio * CHUNKSIZE,
			espacioASetear->tamanio * CHUNKSIZE, value);
}

void eliminarParticion(memCache_t **listaCache){
	memCache_t* victimaAnt = NULL;
	memCache_t* victima = *listaCache;
	memCache_t* ant = *listaCache;
	memCache_t* aux = *listaCache;
	while(aux != NULL){
		if(aux->estado == 'O' && aux->tiempo < victima->tiempo){
			victimaAnt = ant;
			victima = aux;
		}
		ant = aux;
		aux = aux->sig;
	}
	if(victima == *listaCache){
		victima->estado = 'L';

		if(victima->sig != NULL && victima->sig->estado == 'L'){
			unirNodosLibres(victima, &(victima->sig));
			free(victima->key);
			victima->key = NULL;
		}
	}else{
		victima->estado = 'L';
		if(victima->sig->estado == 'L'){
			unirNodosLibres(victima, &(victima->sig));
			free(victima->key);
			victima->key = NULL;
		}
		if(victimaAnt->estado == 'L'){
			unirNodosLibres(victimaAnt, &(victima));
		}
	}
}
